/*
 *  NetworkUtil.cpp
 *  Ringer
 *
 *  Created by Ian Pye on 12/24/07.
 *  Copyright 2007 TriceratopsPowered. All rights reserved.
 *
 */

// BSD headers
#include <iostream>
#include <string>
#include <sys/types.h>
#include <vector>
#include <unistd.h>
#include <sstream>
#include <math.h>

// FUSE
#include <fuse.h>

// Boost
#include <boost/thread/thread.hpp>

// Loggers
#include <log4cxx/logger.h>
#include <log4cxx/basicconfigurator.h>
#include <log4cxx/helpers/exception.h>
#include <log4cxx/propertyconfigurator.h>

// Thrift headers
// Client
#include <protocol/TBinaryProtocol.h>
#include <transport/TSocket.h>
#include <transport/TTransportUtils.h>

// Server
#include <server/TSimpleServer.h>
#include <server/TThreadedServer.h>
#include <transport/TServerSocket.h>
#include <transport/TSSLServerSocket.h>

// Mah Own
#include "Ringer.h"
#include "RingerMetaServer.h"
#include "RingerFS.h"
#include "FileUtil.h"
#include "NetworkUtil.h"
#include "RingerFSClientServer.h"

using namespace std;

using namespace facebook::thrift;
using namespace facebook::thrift::protocol;
using namespace facebook::thrift::transport;
using namespace facebook::thrift::server;

using namespace ringer;
using namespace boost;

// gobals

// Client pointers
static boost::shared_ptr<RingerMetaServerClient> g_ringer_client;
static boost::shared_ptr<TTransport> g_ringer_client_transport;
static std::string g_fs_trust_file;

// Server pointers
shared_ptr<RingerMetaServerIf> g_cs_handler(new RingerFSClientServerHandler());
shared_ptr<TServer> g_client_server;

// basic to_string
template <class T>
inline std::string to_string (const T& t) {
  std::stringstream ss;
  ss << t;
  return ss.str();
}

// Have the server start a servn.
void run_server(){
  g_client_server->serve(); 
}

// Basic networking functions
bool ringer_init_network(ringer::RingerNetworkArgs* net_args, bool disable_clientServer){

  g_fs_trust_file = net_args->trust_file;
  RINGER_GET_CLIENT(net_args->mds_host, net_args->mds_port, g_ringer_client_transport, g_ringer_client, g_fs_trust_file);

  g_ringer_client_transport->open(); 
  
  // next, set the unique identifier for this machine
  GetIpAddress(&g_ringer_client_id);
  // and the prefix:
  SetRingerPathPrefix(g_ringer_client_id.host_name + RINGER_HOST_ROOT_SEP);
  
  // and, put together clientUser info.
  host_t my_host;
  my_host.host_name = g_ringer_client_id.host_name;
  my_host.host_port = net_args->self_port;
  g_clientUser.host = my_host;
  g_clientUser.uid = geteuid();
  g_clientUser.gid = getegid(); // assume groups ids are global in Ringer
  g_clientUser.ringer_uid = geteuid(); // @TODO -- get this by querying the MDS, and getting back the ringer UID
  
  // and send it to the mds
  g_ringer_client->connect_mds(g_clientUser);
  
  // now, set up the Thrift server, unless its disabled.
  if (!disable_clientServer) { 
    RINGER_GET_SERVER(net_args->self_port, g_cs_handler, g_client_server, net_args->public_key, 
        net_args->private_key); 
    boost::thread thrd(&run_server);  // Up, Up, and away!
  }
  
  return true;
}

bool ringer_close_network(){

  // Notify mds we are shutting down.
  g_ringer_client->disconnect_mds(g_clientUser);

  // And do so.
  g_ringer_client_transport->close();
  g_ringer_client.reset(); 
  
  // Also, shut down the server aspect.
  g_client_server->stop(); 
  
  return true;
}

// Testing ops
bool ringer_client_ping(){
  return (g_ringer_client->ping() == RINGER_PING_RESULT? true: false);
}
int ringer_client_add(int a, int b){
  return g_ringer_client->add(a, b);
} 

// Search opts
int ringer_client_search(std::set<ringer::rpath_t>& results, ringer::RingerSearch& terms){
  g_ringer_client->search(results, terms);
  return results.size();
}



// Data ops
/**
  Sychnonousely reads a block of data from another peer over the network.
  
  @return The number of bytes written.
*/
int read_network_file(const char *path, size_t size, off_t offset){

  // Get the rnode for the file
  RNode rn;
  try{
    ringer_client_get(path, rn);
  } catch (TException &tx) {
    LOG4CXX_INFO(g_logger, string("ERROR: ") + tx.what());
    return 0;
  }

  // Accept the asked for block
  shared_ptr<TTransport> transport;
  shared_ptr<RingerMetaServerClient> peer_client;

  RINGER_GET_CLIENT(rn.owner.host.host_name, rn.owner.host.host_port, transport, 
      peer_client, g_fs_trust_file);
    
    
  RingerBlock r_block;
  LOG4CXX_INFO(g_logger, string("Opening: ") + rn.fuse_path);
  LOG4CXX_INFO(g_logger, string("To:  ") + rn.owner.host.host_name);

  try {
    transport->open();
    // use a sychronouse method here to get the data
    peer_client->get_block(r_block, rn.fuse_path, g_clientUser, offset, size );
    transport->close();
    
    LOG4CXX_INFO(g_logger, string("Got:  ") + r_block.block_value);
    LOG4CXX_INFO(g_logger, r_block.block_size);
    
  } catch (TException &tx) {
    LOG4CXX_INFO(g_logger, string("ERROR: ") + tx.what());
    return 0;
  }
  
  // now that we have the file, commit it to disk
  // Localize the file path
  string local_path;
  LocalizeRingerPath(rn.fuse_path.c_str(), local_path);
      
  return WriteReturnValueToDisk(local_path, r_block, offset);
}
/**
  @TODO -- MAKE THIS WORK
  -- WARNING DOES NOT WORK CURRENTLY.
  Tell the owner of the specified rnode to send the block to us.
  
   @return True if there is no trouble, false otherwise. Note this this does NOT mean that the call succeeds, just that there were no networking errors generated.
*/
bool initiate_send_block(const char *path, size_t size, off_t offset){
  return initiate_send_block(g_clientUser, path, size, offset);
}
/**
  @TODO -- MAKE THIS WORK
  Tell the owner of the specified rnode to send the block to the given user.
  
  @return True if there is no trouble, false otherwise. Note this this does NOT mean that the call succeeds, just that there were no networking errors generated.
*/
bool initiate_send_block(const UserInfo& ui, const char *path, size_t size, off_t offset){
  
  // Get the rnode for the ping
  RNode rn;
  ringer_client_get(path, rn);

  // Request we get the asked for file
  shared_ptr<TTransport> transport;
  shared_ptr<RingerMetaServerClient> peer_client;

  RINGER_GET_CLIENT(rn.owner.host.host_name, rn.owner.host.host_port, 
      transport, peer_client, g_fs_trust_file);
  
  LOG4CXX_INFO(g_logger, string("Sending: ") + rn.fuse_path);
  LOG4CXX_INFO(g_logger, string("From:  ") + rn.owner.host.host_name);
  LOG4CXX_INFO(g_logger, string("To:  ") + ui.host.host_name);

  try {
    transport->open();
    peer_client->send_block(ui, rn.fuse_path, size, offset);
    transport->close();
  } catch (TException &tx) {
    LOG4CXX_INFO(g_logger, string("ERROR: ") + tx.what());
    return false;
  }
  return true;
}


/**
  See if the designated owner of an rnode is up, or not.
  
  @return True or False.
*/

bool ping_rnode_owner(const char *path){
  
  // Get the rnode for the ping
  RNode rn;
  bool good_ping = false;
  ringer_client_get(path, rn);

  shared_ptr<TTransport> transport;
  shared_ptr<RingerMetaServerClient> peer_client;

  RINGER_GET_CLIENT(rn.owner.host.host_name, rn.owner.host.host_port, 
      transport, peer_client, g_fs_trust_file);
  
  try {
    transport->open();
    good_ping = (peer_client->ping() == RINGER_PING_RESULT? true: false);
    transport->close();
  } catch (TException &tx) {
    LOG4CXX_INFO(g_logger, string("ERROR: ") + tx.what());
    return false;
  }
  return good_ping;
}

// Metadata ops

/**
  Puts the given rnode into the MDS.
*/
void ringer_client_put(RNode &rn){
  RingerizeFusePath(rn.path.c_str(), rn.path);
  RingerizeFusePath(rn.parent_path.c_str(), rn.parent_path);
  
  // If there is no owner, I guess make this client the owner
  if(!rn.owner.host.host_port){ // assume a default value here of 0.
    rn.owner = g_clientUser;
  }  
    
  g_ringer_client->put(rn, g_clientUser);
}

/**
  Gets the asked for RNode from the MDS.
*/
void ringer_client_get(const char *path, RNode &res){
  try{
    string ringer_path;
    RingerizeFusePath(path, ringer_path);
    g_ringer_client->get(res, ringer_path, g_clientUser);
  } catch (InvalidOperation iop){
    throw iop;
  }
}

/**
  Removes the rnode from the MDS.
*/
void ringer_client_remove(const char *path){
  string ringer_path;
  RingerizeFusePath(path, ringer_path);
  g_ringer_client->remove(ringer_path, g_clientUser);
}

// permission ops
bool ringer_client_get_read(const char *path){
  return ringer_client_get_read(path, g_clientUser);
}

bool ringer_client_get_read(const char *path, const UserInfo& userInfo){
  try{
    string ringer_path;
    RingerizeFusePath(path, ringer_path);
    return g_ringer_client->get_read(ringer_path, userInfo);
  } catch (InvalidOperation iop){
    return false;
  }
}

void ringer_client_end_read(const char *path){
  string ringer_path;
  RingerizeFusePath(path, ringer_path);
  g_ringer_client->end_read( ringer_path, g_clientUser);
}

bool ringer_client_get_write(const char *path){
  return ringer_client_get_write(path, g_clientUser);
}

bool ringer_client_get_write(const char *path, const UserInfo& userInfo){
  try{ 
    string ringer_path;
    RingerizeFusePath(path, ringer_path);
    return g_ringer_client->get_write( ringer_path, userInfo);
  } catch (InvalidOperation iop){
    return false;
  }
}

void ringer_client_end_write(const char *path){
  string ringer_path;
  RingerizeFusePath(path, ringer_path);
  g_ringer_client->end_write( ringer_path, g_clientUser);
}

/**
  Question -- for the case of unequal vector lengths, what should I do?
*/
float dot_product_of_vectors(const vector<simresult_t> &a, const vector<simresult_t> &b){
  float dot_pro = 0;
  for (int i=0; i < min(a.size(), b.size()); i++){
    dot_pro += a[i] * b[i]; 
  }
  return dot_pro;
}

float length_of_vector(const vector<simresult_t> &a){
  float length = 0;
  for (int i=0; i < a.size(); i++){
    length += a[i] * a[i]; 
  }
  return sqrt(length);
}

/**
  Returns the difference between two SimHash results.
  
  In practice this is the angle between the two results.
*/
float find_simularity(const vector<simresult_t> &a, const vector<simresult_t>& b){
  float dot = dot_product_of_vectors(a,b);
  float lengths_product = (length_of_vector(a) * length_of_vector(b));
  if(lengths_product == 0.0){
    return 0;
  } 
  return acos(dot / lengths_product);
}

